package cn.com.jcoo.security;


import cn.com.jcoo.ConvertUtils;
import cn.com.jcoo.security.enums.GroupMode;
import cn.com.jcoo.security.enums.Sm4Algorithm;
import lombok.SneakyThrows;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Base64;
import java.util.concurrent.TimeUnit;

/**
 * SM4加解密工具类：基于BouncyCastle库（BC）
 *
 * @author Jinty
 * @since 1.0
 */
public class Sm4Utils {
	/**
	 * 算法名称：SM4
	 */
	private static final String SM4_ALGORITHM = "SM4";

	/**
	 * 默认算法模式
	 */
	private static final String DEFAULT_SM4_ALGORITHM_MODE = Sm4Algorithm
			.nameOf(Sm4Algorithm.CBC_PKCS5PADDING);

	/**
	 * 默认的随机数种子（用于生成key、iv）
	 */
	private static final String DEFAULT_SEED = "random_seed";

	/**
	 * 默认key的字节长度 128-32位16进制；256-64位16进制
	 * 国密即国家密码局认定的国产密码算法。主要有SM1，SM2，SM3，SM4_ALGORITHM。密钥长度和分组长度均为128位。 SM1
	 * 为对称加密。其加密强度与AES相当。该算法不公开，调用该算法时，需要通过加密芯片的接口进行调用。
	 * SM2为非对称加密，基于ECC。该算法已公开。由于该算法基于ECC，故其签名速度与秘钥生成速度都快于RSA。 ECC 256位（SM2采用的就是ECC
	 * 256位的一种）安全强度比RSA 2048位高，但运算速度快于RSA。 SM3 消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位。
	 * SM4_ALGORITHM 无线局域网标准的分组数据算法。对称加密，密钥长度和分组长度均为128位。
	 */
	private static final int DEFAULT_KEY_SIZE = 128;

	/**
	 * 默认向量的字节长度
	 */
	private static final int DEFAULT_IV_SIZE = 16;

	/**
	 * 默认的key
	 */
	private static final byte[] DEFAULT_KEY_BYTE;

	/**
	 * 默认的向量
	 */
	private static final byte[] DEFAULT_IV_BYTE;

	static {
		// 使用BouncyCastle库提供的加解密服务
		if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
			Security.addProvider(new BouncyCastleProvider());
		}
		try {
			// 默认key
			DEFAULT_KEY_BYTE = generateSm4Key();
			// 默认向量
			DEFAULT_IV_BYTE = SecurityUtils.generateIV(DEFAULT_SEED, DEFAULT_IV_SIZE);
		}
		catch (NoSuchAlgorithmException | NoSuchProviderException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 生成默认密钥
	 */
	private static byte[] generateSm4Key()
			throws NoSuchAlgorithmException, NoSuchProviderException {
		return generateSm4Key(DEFAULT_SEED, DEFAULT_KEY_SIZE);
	}

	/**
	 * 随机生成默认长度的密钥
	 *
	 * @param seed 随机数种子
	 * @return Sm4密钥
	 */
	private static byte[] generateSm4Key(String seed) throws NoSuchAlgorithmException {
		return generateSm4Key(seed, DEFAULT_KEY_SIZE);
	}

	/**
	 * 随机生成指定长度的密钥
	 *
	 * @param seed 随机数种子
	 * @param keySize 密钥大小
	 * @return Sm4密钥
	 */
	private static byte[] generateSm4Key(String seed, int keySize)
			throws NoSuchAlgorithmException {
		SecretKey secretKey = SecurityUtils.generateKey(SM4_ALGORITHM, seed, keySize);
		return secretKey.getEncoded();
	}

	private static Cipher generateSm4Cipher(String algorithm, int cipherMode,
			byte[] keyBytes, byte[] iv) throws Exception {
		// 以与 provider 无关的方式指定一个密钥
		Key key = new SecretKeySpec(keyBytes, SM4_ALGORITHM);
		// 加解密引擎初始化
		if (algorithm.contains(GroupMode.ECB.getReg())) {
			return SecurityUtils.generateCipher(algorithm, cipherMode, key);
		}
		else {
			// 以与 provider 无关的方式指定（向量）算法参数
			AlgorithmParameterSpec ivParameterSpec = new IvParameterSpec(iv);
			return SecurityUtils.generateCipher(algorithm, cipherMode, key,
					ivParameterSpec);
		}
	}

	/**
	 * 加密
	 *
	 * @param algorithmName 加密算法名称
	 * @param key 密匙
	 * @param iv IV
	 * @param data 加密数据
	 * @return 加密后数据
	 * @throws Exception 加密异常
	 */
	private static byte[] encrypt(String algorithmName, byte[] key, byte[] iv,
			byte[] data) throws Exception {
		return sm4core(algorithmName, Cipher.ENCRYPT_MODE, key, iv, data);
	}

	private static byte[] encrypt(byte[] bytes) {
		return sm4core(DEFAULT_SM4_ALGORITHM_MODE, Cipher.ENCRYPT_MODE, DEFAULT_KEY_BYTE,
				DEFAULT_IV_BYTE, bytes);
	}

	/**
	 * 指定algorithmName、key、iv对data进行解密
	 */
	private static byte[] decrypt(String algorithmName, byte[] key, byte[] iv,
			byte[] data) {
		return sm4core(algorithmName, Cipher.DECRYPT_MODE, key, iv, data);
	}

	/**
	 * 使用CBC_PKCS5PADDING、默认key、默认向量对data进行解密
	 */
	public static byte[] decrypt(byte[] data) {
		try {
			return sm4core(DEFAULT_SM4_ALGORITHM_MODE, Cipher.DECRYPT_MODE,
					DEFAULT_KEY_BYTE, DEFAULT_IV_BYTE, data);
		}
		catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	public static String encryptStr(String str) {
		if (!StringUtils.hasLength(str)) {
			return str;
		}
		try {
			byte[] encrypt = encrypt(str.getBytes());
			return Base64.getEncoder().encodeToString(encrypt);
		}
		catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	public static String decryptStr(String str) {
		if (!StringUtils.hasLength(str)) {
			return str;
		}
		byte[] decrypt = decrypt(Base64.getDecoder().decode(str));
		return new String(decrypt);
	}

	public static String encryptNumber(Number number) throws Exception {
		if (number == null) {
			return null;
		}
		byte[] encrypt = encrypt(number.toString().getBytes(StandardCharsets.UTF_8));
		return Base64.getEncoder().encodeToString(encrypt);
	}

	public static <T extends Number> T decryptNumber(String str, Class<T> numClass) {
		if (!StringUtils.hasLength(str)) {
			return null;
		}
		try {
			byte[] decrypt = decrypt(Base64.getDecoder().decode(str));
			return getNumber(new String(decrypt), numClass);
		}
		catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	private static <T extends Number> T getNumber(String value, Class<T> nClass) {
		if (null == value) {
			return null;
		}
		if (nClass == Integer.class) {
			return (T) ConvertUtils.toInt(value);
		}
		else if (nClass == Long.class) {
			return (T) ConvertUtils.toLong(value);
		}
		else if (nClass == BigDecimal.class) {
			return (T) ConvertUtils.toBigDecimal(value);
		}
		return (T) ConvertUtils.toNumber(value);
	}

	public static void encryptFile(String sourcePath, String targetPath)
			throws Exception {
		InputStream inputStream = encryptFile(
				Files.newInputStream(Paths.get(sourcePath)));
		OutputStream outputStream = Files.newOutputStream(Paths.get(targetPath));
		FileCopyUtils.copy(inputStream, outputStream);
	}

	public static void decryptFile(String sourcePath, String targetPath)
			throws Exception {
		InputStream inputStream = decryptFile(
				Files.newInputStream(Paths.get(sourcePath)));
		OutputStream outputStream = Files.newOutputStream(Paths.get(targetPath));
		FileCopyUtils.copy(inputStream, outputStream);
	}

	public static InputStream encryptFile(InputStream inputStream) throws Exception {
		Cipher cipher = generateSm4Cipher(DEFAULT_SM4_ALGORITHM_MODE, Cipher.ENCRYPT_MODE,
				DEFAULT_KEY_BYTE, DEFAULT_IV_BYTE);
		return new CipherInputStream(inputStream, cipher);
	}

	public static InputStream decryptFile(InputStream inputStream) throws Exception {
		Cipher cipher = generateSm4Cipher(DEFAULT_SM4_ALGORITHM_MODE, Cipher.DECRYPT_MODE,
				DEFAULT_KEY_BYTE, DEFAULT_IV_BYTE);
		return new CipherInputStream(inputStream, cipher);
	}

	/**
	 * Sm4加解密
	 *
	 * @param algorithmName 加解密算法
	 * @param type 加密/解密
	 * @param key 密钥
	 * @param iv 初始向量
	 * @param data 数据
	 * @return 加密/解密后的参数
	 */
	@SneakyThrows
	private static byte[] sm4core(String algorithmName, int type, byte[] key, byte[] iv,
			byte[] data) {
		Cipher cipher = generateSm4Cipher(algorithmName, type, key, iv);
		// 执行加解密
		return cipher.doFinal(data);
	}

	public static void main(String[] args) throws Exception {

		String text = "123456";
		for (Sm4Algorithm algorithm : Sm4Algorithm.values()) {
			String algorithmName = Sm4Algorithm.nameOf(algorithm);
			try {
				System.out.println("SM4加密算法： " + algorithmName);
				System.out.println("SM4加密原始数据： " + text);
				System.out.println("SM4加密key： "
						+ Base64.getEncoder().encodeToString(DEFAULT_KEY_BYTE));
				System.out.println("SM4加密iv： "
						+ Base64.getEncoder().encodeToString(DEFAULT_IV_BYTE));

				byte[] encrypt = Sm4Utils.encrypt(algorithmName, DEFAULT_KEY_BYTE,
						DEFAULT_IV_BYTE, text.getBytes());
				System.out.println("xx:"
						+ org.bouncycastle.util.encoders.Base64.toBase64String(encrypt));
				System.out.println(
						"SM4加密数据密文： " + Base64.getEncoder().encodeToString(encrypt));

				// SM4解密
				byte[] decrypt = Sm4Utils.decrypt(algorithmName, DEFAULT_KEY_BYTE,
						DEFAULT_IV_BYTE, encrypt);
				System.out.println("SM4解密数据： " + new String(decrypt));
			}
			catch (Exception e) {
				if (e instanceof IllegalBlockSizeException) {
					System.err.println("SM4解密数据：算法 " + algorithmName + "数据需自己手工对齐");
				}
				else {
					System.err.println(
							"SM4解密数据：算法 " + algorithmName + "::" + e.getMessage());
				}
			}
			finally {
				System.err.println("---------------------------------------");
				TimeUnit.SECONDS.sleep(1);
			}
		}

		// Sm4Utils.encryptFile("E:\\11047\\Desktop\\test.txt",
		// "E:\\11047\\Desktop\\test_en.txt");
		//
		// Sm4Utils.decryptFile("E:\\11047\\Desktop\\test_en.txt",
		// "E:\\11047\\Desktop\\test_de.txt");
	}
}
